/**
 * 创建canvas画布
 * @param id 标签id
 * @returns webgl
 */
export const useCanvas = (props: WebGL.CanvasProps) => {
  if (!props.id) {
    throw new Error('Canvas missing ID.')
  }
  const canvas = document.querySelector<HTMLCanvasElement>(`#${props.id}`)
  if (!canvas) {
    throw new Error('Canvas not found.')
  }
  const width = props.canvasWidth || window.innerWidth
  const height = props.canvasHeight || window.innerHeight
  canvas.width = width
  canvas.height = height
  const gl = canvas.getContext('webgl')
  if (!gl) {
    throw new Error('You cannot use WebGL!')
  }
  // 告诉WebGL怎样把提供的gl_Position裁剪空间坐标对应到画布像素坐标，
  // 通常我们也把画布像素坐标叫做屏幕空间
  gl.viewport(0, 0, width, height)
  return gl
}

/**
 * canvas刷屏
 * @param gl 渲染上下文
 * @param color 指定清除颜色
 */
export const useCanvasClear = (gl: WebGLRenderingContext, color?: WebGL.Color4) => {
  // 默认使用不透明的黑色清除所有图像
  color = color || { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 }
  gl.clearColor(color.red, color.green, color.blue, color.alpha)
  // 清除缓冲区
  gl.clear(gl.COLOR_BUFFER_BIT)
}

/**
 * 创建着色器
 * @param gl 渲染上下文
 * @param type 着色器类型
 * - gl.VERTEX_SHADER
 * - gl.FRAGMENT_SHADER
 * @param source 数据源
 * @returns shader
 */
export const useCreateShader = (gl: WebGLRenderingContext, type: number, source: string) => {
  // 创建着色器对象
  const shader = gl.createShader(type)
  if (!shader) {
    throw new Error('Failed to create shader.')
  }
  // 提供数据源
  gl.shaderSource(shader, source)
  // 编译 生成着色器
  gl.compileShader(shader)
  const compilerStatus = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
  if (compilerStatus) {
    return shader
  }
  console.info(gl.getShaderInfoLog(shader))
  gl.deleteShader(shader)
}

/**
 * 创建着色器程序
 * @param gl 渲染上下文
 * @param vertexShader 顶点着色器
 * @param fragmentShader 片元着色器
 * @returns program
 */
export const useCreateProgram = (
  gl: WebGLRenderingContext,
  vertexShader: WebGLShader,
  fragmentShader: WebGLShader
) => {
  const program = gl.createProgram()
  if (!program) {
    throw new Error('Failed to create program.')
  }
  // 向着色器程序添加顶点着色器和片元着色器
  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)
  // 将着色器链接到着色器程序
  gl.linkProgram(program)
  const linkStatus = gl.getProgramParameter(program, gl.LINK_STATUS)
  if (linkStatus) {
    return program
  }
  console.info(gl.getProgramInfoLog(program))
  gl.deleteProgram(program)
}

/**
 * 创建WebGL着色器程序
 * @param id canvas id
 * @param vertexShaderSource 顶点着色器源码
 * @param fragmentShaderSource 片元着色器源码
 * @returns engine
 */
export const useWebGLEngine = (
  id: string,
  vertexShaderSource: string,
  fragmentShaderSource: string
): WebGL.WebGLEngine => {
  const gl = useCanvas({id})
  // 清屏
  useCanvasClear(gl)
  // 1. 创建顶点着色器、片元着色器
  const vertexShader = useCreateShader(gl, gl.VERTEX_SHADER, vertexShaderSource)
  const fragmentShader = useCreateShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource)
  if (!vertexShader || !fragmentShader) {
    throw new Error('useWebGLEngine, Failed to create shader.')
  }
  // 2. 现在我们已经在GPU上创建了一个GLSL着色程序，
  // 我们还需要给它提供数据。 
  // WebGL的主要任务就是设置好状态并为GLSL着色程序提供数据
  const program = useCreateProgram(gl, vertexShader, fragmentShader)
  if (!program) {
    throw new Error('useWebGLEngine, Failed to create program.')
  }

  // 告诉它用我们之前写好的着色程序（一个着色器对）
  gl.useProgram(program)
  
  const engine: WebGL.WebGLEngine = {
    gl,
    program
  }
  return engine
}